home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-09-04 | 50.9 KB | 1,190 lines |
- CHAPTER 10
-
- KEY MEMORY AREAS IN THE PC
-
-
- Two very important BASIC keywords that are sadly neglected by many
- programmers are PEEK and POKE. Most people understand that these let you
- read from and write to memory locations. But what are they really good
- for? The whole point of a high-level language like BASIC is to avoid such
- direct memory access, and to many programmers these commands may seem like
- an enigma.
- In most cases, you *don't* need to access memory with PEEK and POKE.
- Unlike C and assembly language that require direct memory operations to
- process strings and arrays, BASIC includes a full complement of commands
- for this. However, there is at least one important use for PEEK and POKE
- that cannot be accomplished in any other way: accessing low memory.
- The portion of memory in every PC that begins at Hex address 0000:0400
- is called the *BIOS Data Area*, and it contains much useful information.
- For example, the equipment word at address &H410 tells how many diskette
- drives are installed, and how many parallel and serial ports there are.
- The keyboard status flags at address &H417 can be read (and written), to
- reflect whether the Caps Lock and NumLock states are active.
- In this chapter I will describe all of the low memory locations that are
- relevant to a BASIC program, and present numerous practical examples to
- show how this data can be utilized. This is by no means a complete list
- of every BIOS data address that is available in the PC. Rather, I have
- purposely limited it to those that I have found useful.
-
-
- IMPROVING PEEK AND POKE
- =======================
-
- One potential limitation that needs to be addressed first is how to access
- full words of data. BASIC's PEEK and POKE operate on single bytes only,
- and reading or writing two bytes at a time is a messy proposition at best.
- Chapter 9 introduced a pair of routines called PeekWord and PokeWord,
- that allowed accessing memory a word at a time. In the context those were
- presented, a fair amount of code could be saved by consolidating the
- necessary code into a subprogram or function. But in the interest of speed
- and even further code size reductions, the following assembly language
- routines are better still.
-
- ;PEEKPOKE.ASM, simplifies access to full words
-
- .Model Medium, Basic
- .Code
-
- PeekWord Proc Uses ES, SegAddr:DWord
- Les BX,SegAddr ;load the segment and address
- Mov AX,ES:[BX] ;read the word into AX
- Ret ;return to BASIC
- PeekWord Endp
-
-
- PokeWord Proc Uses ES, SegAddr:DWord, Value:Word
- Les BX,SegAddr ;load the segment and address
- Mov AX,Value ;and the new value to store there
- Mov ES:[BX],AX ;write the value into memory
- Ret ;return to BASIC
- PokeWord Endp
- End
-
- Both of these routines expect the parameters to be passed by value, for
- faster speed and smaller code. Therefore, you will declare them as
- follows:
-
- DECLARE FUNCTION PeekWord%(BYVAL Segment%, BYVAL Address%)
- DECLARE SUB PokeWord(BYVAL Segment%, BYVAL Address%, BYVAL Value%)
-
- Then to read a word of memory--say, the address of the LPT1 printer adapter
- at address &H408--PeekWord would be invoked like this:
-
- LPT1Addr% = PeekWord%(0, &H408)
-
- And to write the letter "A" in the lower left corner of a color display
- screen in white on blue you could use PokeWord, thus:
-
- CALL PokeWord(&HB800, 3998, &H1741)
-
- Notice that PeekWord returns a negative value for numbers greater than
- 32767. This is normal, as explained in Chapter 2. However, the same
- negative value that PeekWord returns can be used as an argument to PokeWord
- with the correct results.
-
-
- LOW MEMORY ADDRESSES
- ====================
-
- The sections that follow are organized by category, since this is how low
- memory is arranged in the PC. That is, one section discusses the RS-232
- communications data area, the next shows the portion of memory used by the
- printer adapters, and so forth. Each address is listed in ascending order;
- by convention, Hex notation is used exclusively for these addresses. In
- all of the examples shown here, you will use a segment value of zero.
- It is important to understand that besides memory addresses that are
- accessed with PEEK and POKE (or in this case their full-word equivalents),
- the IBM PC family also has a series of input and output ports. These ports
- are accessed using INP and OUT commands instead of PEEK and POKE. I
- mention this here because ports are referred to in several places in the
- discussions that follow. In particular, the communications ports that are
- exchanged in the next section are in fact port numbers, and not memory
- addresses. Some useful port numbers are given at the end of this chapter,
- along with code examples that show how to read from and write to them.
- Table 10-1 provides a summary of all the low memory addresses that are
- described in this chapter.
-
- Address Meaning
- ======= ==========================================
- &H400 2 bytes, COM1 port number
- &H402 2 bytes, COM2 port number
- &H404 2 bytes, COM3 port number
- &H406 2 bytes, COM4 port number
-
- &H408 2 bytes, LPT1 port number
- &H40A 2 bytes, LPT2 port number
- &H40C 2 bytes, LPT3 port number
- &H40E 2 bytes, LPT4 port number
-
- &H410 2 bytes, Equipment List
- &H413 2 bytes, installed memory (K)
- &H417 2 bytes, keyboard status
- &H418 2 bytes, enhanced keyboard status
-
- &H41A 2 bytes, keyboard buffer head pointer
- &H41C 2 bytes, keyboard buffer tail pointer
- &H41E 30 bytes, keyboard buffer
-
- &H43F 1 byte, diskette motor on indicator
- &H440 1 byte, diskette motor countdown timer
-
- &H449 1 byte, current video mode
- &H44A 2 bytes, current screen width (columns)
- &H44C 2 bytes, current video page size (bytes)
- &H462 1 byte, current video page number
- &H463 2 bytes, CRT controller port number
-
- &H46C 4 bytes, long integer system timer count
-
- &H478 4 bytes, LPT1 - LPT4 timeout values
-
- &H484 1 byte, EGA/VGA screen height (rows)
- &H485 2 bytes, character height (scan lines)
- &H487 1 byte, EGA/VGA Features bits
-
- &H4F0 16 bytes, Inter-Application Area
-
- &H500 1 byte, PrtSc busy flag
-
- &H504 1 byte, active drive for one-diskette PC
-
- Table 10-1: Key low memory addresses in the PC.
-
- COMMUNICATIONS PORT ADDRESSES
- =============================
-
- The four words starting at address &H400 hold the port numbers for each
- installed RS-232 communications adapter. For example, the port number for
- COM1 is contained in the word at address &H400, and the port number for
- COM3 is at address &H404. Because these port numbers are words rather than
- bytes, the COM1 port number is contained in both &H400 and &H401. Thus,
- COM2 starts at address &H402, and COM3 starts at &H404.
- BASIC allows you to open only COM ports 1 and 2; however by exchanging
- these addresses you can substitute ports 3 and 4 if necessary. The
- complete program that follows first swaps the port numbers for COM1 and
- COM3, and then opens COM1 for output. Since the port numbers are swapped,
- it is actually COM3 that is being opened.
-
-
- DEFINT A-Z
- DECLARE FUNCTION PeekWord% (BYVAL Segment, BYVAL Address)
- DECLARE SUB PokeWord (BYVAL Segment, BYVAL Address, BYVAL Value)
-
- COM1 = PeekWord%(0, &H400) 'save COM1 port number
- COM3 = PeekWord%(0, &H404) 'save COM3 port number
- CALL PokeWord(0, &H400, COM3) 'assign COM3 to COM1
- CALL PokeWord(0, &H404, COM1) 'and then COM1 to COM3
-
- OPEN "COM1:1200,N,8,1,RS,DS" FOR RANDOM AS #1
- PRINT #1, "ATDT 1-555-1212" 'dial information
- CLOSE #1
-
- CALL PokeWord(0, &H400, COM1) 'restore the original values
- CALL PokeWord(0, &H404, COM3)
-
-
- PRINTER PORT ADDRESSES
- ======================
-
- The four printer port numbers start at address &H408, and they are similar
- to those used to hold the communications ports and may also be exchanged
- if necessary. For example, if you have a program that uses LPRINT
- commands, all printed output will be sent to LPT1. If at some later time
- you want to use the same program with LPT2, you can exchange the port
- numbers instead of having to rewrite the program. A short code fragment
- that does this is shown following.
-
-
- DEFINT A-Z
- DECLARE FUNCTION PeekWord% (BYVAL Segment, BYVAL Address)
- DECLARE SUB PokeWord (BYVAL Segment, BYVAL Address, BYVAL Value)
-
- LPT1 = PeekWord%(0, &H408) 'save LPT1 port number
- LPT2 = PeekWord%(0, &H40A) 'save LPT2 port number
- CALL PokeWord(0, &H408, LPT2) 'assign LPT2 to LPT1
- CALL PokeWord(0, &H40A, LPT1) 'and LPT1 to LPT2
-
- LPRINT "This is printed on LPT2"
- CALL PokeWord(0, &H408, LPT1) 'restore the original values
- CALL PokeWord(0, &H40A, LPT2)
- LPRINT "And now we're back to LPT1" 'prove it worked
-
-
- Like the communications port addresses, each printer port address is a
- full word, so while the first is located at address &H408, the second is
- at &H40A. You will also find PeekWord useful because it does not require
- you to change the current DEF SEG setting. Although there is no harm in
- assigning a new DEF SEG value in most cases, it is not easy to restore it
- to the original setting. Therefore, when writing reusable subprograms and
- functions that need to access memory, you don't have to worry about
- affecting a subsequent PEEK or BLOAD in the main program.
-
-
- SYSTEM DATA
- ===========
-
- One of the most valuable data items in low memory is the equipment list
- in the word starting at address &H410. The information contained here is
- bit coded, to indicate which and how many peripherals are installed in the
- host PC. Figure 10-1 shows the organization of this word. Bits not
- identified are either reserved, or not particularly useful.
-
-
- 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 <- bit numbers
- x x -------------------------------------------- printers
- x -------------------------------------- 1 = game port
- x x x -------------------------- serial ports
- x x -------------------- diskette *
- x x -------------- video mode
- x ------ coprocessor
- x -- diskette *
-
- * If Bit 0 is set, then bits 6 and 7 together reflect the number of
- diskette drives *less one*. If Bit 0 is clear then no diskette drives are
- installed.
-
- Figure 10-1: The organization of the equipment list word at address &H410.
-
-
- Because the data in this word is bit coded, you must use AND to extract
- the necessary information. For example, to see if a math coprocessor is
- installed you must turn off all but bit 1, and see if the result is zero
- or not:
-
- IF PeekWord%(0, &H410) AND 2 THEN
- PRINT "A coprocessor is installed."
- ELSE
- PRINT "Sorry, no coprocessor detected."
- END IF
-
- This brings up an important point, because it is not immediately obvious
- what values you should use to isolate the various bits in a word. It would
- be terrific if Microsoft BASIC offered the ability to handle binary values
- directly. The Microsoft Macro Assembler allows this, as does PowerBasic.
- In the absence of &B and a BIN$ function, the following short function can
- be used to determine the correct integer value for a given sequence of
- binary bits.
-
-
- FUNCTION Bin% (Bit$) STATIC
- Temp& = 0
- Length = LEN(Bit$)
- FOR X = 1 TO Length
- IF MID$(Bit$, Length - X + 1, 1) = "1" THEN
- Temp& = Temp& + 2 ^ (X - 1)
- END IF
- NEXT
- IF Temp& > 32767 THEN
- Bin% = Temp& - 65536
- ELSE
- Bin% = Temp&
- END IF
- END FUNCTION
-
-
- Given a string of binary digits of the form "01011001", the Bin function
- returns an equivalent integer value. You could add this function to your
- programs, or use it to determine constant values ahead of time. For
- example, to determine the number of diskette drives that are installed
- requires isolating bits 6 and 7. This is simple in assembly language,
- where you can specify an AND mask using 11000000b as a value. The example
- below obtains the equipment word, and then uses the Bin function to disable
- all but bits 6 and 7.
-
- Equipment = PeekWord%(0, &H410)
- Floppies = 1 + (Equipment AND Bin%("11000000")) \ 64
- PRINT Floppies; "diskette drive(s) installed"
-
- Although the Bin function is used in the code, I recommend that you create
- a simple test program first, to determine the value of 11000000 (192) once
- ahead of time. Then, the Bin function can be omitted from the final
- program and the second line would be changed as follows:
-
- Floppies = 1 + (Equipment AND 192) \ 64
-
- Notice the use of parentheses to force BASIC to combine Equipment and the
- number 192 before dividing by 64 with AND. If these are omitted BASIC
- will instead combine Equipment with the result of 192 divided by 64, which
- is not correct.
- One final technique you should understand is how to shift bits into the
- correct position to obtain the actual value the bits represent. Treated
- as bits alone, the number of diskette drives is represented as 00, 01, 10,
- or 11, and the decimal equivalents for these binary numbers are 0, 1, 2,
- and 3. But because of their positioning in the equipment word, the bits
- must be shifted to the right six places. After all, the value 11000000
- (192) is certainly not the same as the value 11 (3).
- This is handled simply and elegantly using integer division as shown.
- To shift a number right one position divide it by 2; to shift right 2
- places divide by 4, and so forth. Since the diskette bits need to be
- shifted six places, the equipment variable is divided by 64 after AND is
- used to mask off the unrelated bits. Likewise, to shift bits left you can
- multiply by 2, 4, 8, and so forth. The number to use when dividing or
- multiplying can also be determined by raising 2 to the number of bits
- power. For example, to shift a number right five places you would divide
- by 2 ^ 5 = 32.
- A problem arises when dealing with the highest order bit, because to
- BASIC this bit implies a negative number. Therefore, when bit 15 is set,
- dividing will not produce the expected results. One workaround that is
- admittedly clumsy is to test that bit explicitly, then mask it off and
- shift the bits as needed, and finally use an IF test to see if the bit had
- been set. The only place this is necessary in the equipment list is when
- reading the number of parallel printers that are present. The first
- example below reports the number of serial ports, and the second tells how
- many parallel ports are installed.
-
-
- Equipment = PeekWord%(0, &H410)
- Serial = (Equipment AND Bin%("11000000000")) \ 512
- PRINT Serial; "serial port(s) installed"
-
-
- IF Equipment AND Bin%("1000000000000000") THEN
- HiBitSet = -1
- END IF
- Parallel = (Equipment AND Bin%("0100000000000000")) \ 16384
- IF HiBitSet THEN Parallel = Parallel + 2
- PRINT Parallel; "parallel port(s) installed"
-
-
- In the interest of completeness I should point out that it is not strictly
- necessary to manipulate bit 15 when accessing the equipment word. Since
- none of the information straddles a byte boundary, BASIC's PEEK can in fact
- be used to read just the high byte. Since a byte value is never higher
- than 255, the entire issue of saving and then masking that bit can be
- avoided. But there are other situations you may encounter where an entire
- word must be processed and the highest bit may be set.
- The final useful item in the equipment word is the initial video mode.
- I've seen many programmers read use information to determine if a color
- or monochrome monitor is installed like this:
-
- DEF SEG = 0
- IF (PEEK(&H410) AND &H30) = &H30 THEN
- ' monochrome
- ELSE
- ' color
- END IF
-
- There are two problems with this approach. The most serious is that this
- reflects the monitor that was active when the PC was first powered up.
- These days, many people have two monitors connected to their PC, and you
- usually need to know which is currently active. The other problem is this
- requires more code than the better method I showed in Chapter 6 which reads
- the port address of the currently active video adapter:
-
- DEF SEG = 0
- IF PEEK(&H463) = &HB4 THEN
- ' monochrome
- ELSE
- ' color
- END IF
-
- Besides the equipment word at address &H410, another word at address &H413
- holds the amount of memory that is installed in KiloBytes. Note that this
- word does not reflect any extended or expanded memory that may be present.
- Also note that a much better indicator of how much memory is actually
- available to a program is BASIC's FRE(-1) function. The short code
- fragment below shows how to determine the total DOS-accessible memory that
- is installed.
-
- TotalK = PeekWord%(0, &H413)
- PRINT TotalK; "K Bytes present in this PC."
-
-
- KEYBOARD DATA
- =============
-
- As with the equipment word, the keyboard data area also maintains bit-coded
- information. However, this word indicates the setting of the various
- keyboard shift states. Unlike many of the other addresses in the BIOS data
- area, some of these bits may be written to as well as read from.
- The byte at address &H417 shows the current status of all of the shift
- keys, and the lower four bits may be either read or written. The remaining
- bits in this byte should not be written to, nor should you alter any of the
- bits in the next byte at address &H418. Figure 10-2 shows the meaning of
- each bit in the byte at address &H417, and Figure 10-3 shows the bits at
- address &H418 that relate to extended keyboards only.
-
- 7 6 5 4 3 2 1 0 <-- bits
-
- x --------------------------------- Insert state
- x ----------------------------- Caps Lock
- x ------------------------- Num Lock
- x --------------------- Scroll Lock
- x ----------------- Alt key
- x ------------- Ctrl key
- x --------- Left Shift key
- x ----- Right Shift key
-
- Figure 10-2: The organization of the keyboard data byte at address &H417.
-
-
-
-
-
- 7 6 5 4 3 2 1 0 <-- bits
-
- x --------------------------------- Insert
- x ----------------------------- Caps Lock
- x ------------------------- Num Lock
- x --------------------- Scroll Lock
- x ----------------- Pause state
- x ------------- Sys Req
- x --------- Left Alt key
- x ----- Left Ctrl key
-
- Figure 10-3: The organization of the extended keyboard data byte at address
- &H418.
-
- The various flags in the upper four bits at address &H417 are toggled on
- and off by the BIOS each time the corresponding keys are pressed. For
- example, bit 6 is set while the Caps Lock is active, and bit 5 is clear
- when Num Lock is not in effect. Note, however, that the Insert flag is of
- no practical use, and you should not rely on that bit in your programs.
- If you are writing an input routine (or using the one shown in Chapter 6)
- you should keep track of the insert status manually.
- The lower four bits indicate the current state of the various shift
- keys, and they are set only while the associated key is actually being
- pressed. Bits in the next word at address &H418 let you determine which
- Alt and Ctrl keys are pressed, for keyboards that have more than one of
- those keys. In most cases you will probably just want to know if these
- keys are active, and not distinguish between the left and the right key.
- Therefore, you will usually ignore the extended keyboard information,
- unless you need to detect the SysReq key.
- As with the equipment list, you will use a combination of PeekWord (or
- PEEK) to read all of the flags, and then use AND to isolate just those bits
- you care about. Because there is only one bit that corresponds to each
- keyboard state flag, it is not necessary to divide or multiply to convert
- multiple bits into a number.
- The examples below show how to test each of the bits in the byte at
- address &H417, without regard to the extra Ctrl and Alt key information
- contained at address &H418.
-
- CLS
- PRINT "Press the various Shift and Lock keys, ";
- PRINT "then press Escape to end this madness."
- COLOR 0, 7
-
- DO
- Status = PeekWord%(0, &H417)
-
- LOCATE 10, 1
- IF Status AND 1 THEN
- PRINT "RightShift"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 11
- IF Status AND 2 THEN
- PRINT "Left Shift"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 21
- IF Status AND 4 THEN
- PRINT "Ctrl key"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 31
- IF Status AND 8 THEN
- PRINT "Alt key"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 41
- IF Status AND 16 THEN
- PRINT "ScrollLock"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 51
- IF Status AND 32 THEN
- PRINT "Num Lock"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 61
- IF Status AND 64 THEN
- PRINT "Caps Lock"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOCATE 10, 71
- IF Status AND 128 THEN
- PRINT "Insert"
- ELSE
- GOSUB ClearIt
- END IF
-
- LOOP UNTIL INKEY$ = CHR$(27)
- COLOR 7, 0
- END
-
- ClearIt:
- COLOR 7, 0
- PRINT SPACE$(10);
- COLOR 0, 7
- RETURN
-
- As you can see, to read a single bit you use AND to isolate it from the
- rest, and then test if the result is non-zero. Setting a bit requires
- slightly more work, because it is important not to disturb the other bits
- in that byte. This requires that you first read the current information,
- change only the bit or bits of interest, and then write the modified data
- back to the same location. The next short example shows how to turn the
- CapsLock state on and then off again.
-
-
- CurStatus = PeekWord%(0, &H417)
- NewStatus = CurStatus OR Bin%("1000000")
- CALL PokeWord(0, &H417, NewStatus)
-
- PRINT "Press a key to turn off CapsLock"
- WHILE INKEY$ = "": WEND
-
- NewStatus = NewStatus AND Bin%("10111111")
- CALL PokeWord(0, &H417, NewStatus)
-
-
- Notice the difference between how OR is used in the first example, and how
- AND is used in the second one. In the first case we want to set a bit,
- so only that bit is specified in the binary mask. The remaining bits stay
- the same as they were--if they are already set then OR will leave them that
- way. But to turn off the CapsLock bit requires that all of the mask bits
- be set *except* the one you wish to force off. Other bits that were
- already on will remain on after being combined with AND and 1.
-
-
- THE KEYBOARD BUFFER
-
- The next group of low memory keyboard addresses relate to the keyboard
- buffer. As you undoubtedly know, every PC has a keyboard buffer that can
- hold up to fifteen keystrokes. When a program is off doing something and
- is unable to read the keyboard, the BIOS keyboard routines will store keys
- that have been typed. Then, when the program finally gets around to
- reading the keyboard, they are waiting there to be read. The keyboard
- buffer is therefore also called the *type-ahead* buffer.
- A series of 34 bytes are set aside for the keyboard buffer. Two words
- (four bytes) are used to hold the current head and tail pointers that show
- where the next key will be read from, and where the next will be stored.
- The current head address is stored at address &H41A and the tail at address
- &H41C. Thirty additional bytes are used to store the actual keystrokes,
- with two bytes used for each. The keyboard buffer is called a *circular
- buffer*, because the start and end points are constantly revolving.
- When a PC is first powered up, the head of the buffer holds the address
- &H41E, which is the start of the buffer memory area. The tail is also
- initially set to that same address, until a key is pressed. When that
- happens, the tail pointer is advanced by 2, and the character and its scan
- code are placed into the buffer. Each time a new key is pressed the
- character and scan code are added to the end of the buffer and the tail
- pointer is advanced by two; each time a key is read by an application the
- word at the current head is returned and the head pointer is advanced.
- Note that the head and tail addresses assume a segment of &H40, rather
- than zero. Therefore, the actual values stored range from &H1E through
- &H3A rather than &H41E through &H43A. Of course, address 0000:041E is the
- same as address 0040:001E, and you can think of the buffer address either
- way. I usually treat all of low memory as being located in segment 0,
- because that can often save a byte of code. BASIC (or assembly language,
- for that matter) can pass the number zero by value using only three bytes,
- compared to the four bytes needed to pass any other number.
- The program below shows how to determine the number of keys that are
- currently pending in the buffer, and also which one will be returned next.
-
-
- CLS
- PRINT "You have two seconds to press a few keys..."
- Pause! = TIMER
- WHILE Pause! + 2 > TIMER: WEND
-
- BufferHead = PeekWord%(0, &H41A)
- BufferTail = PeekWord%(0, &H41C)
-
- NumKeys = (BufferTail - BufferHead) \ 2
- IF NumKeys < 0 THEN NumKeys = NumKeys + 16
- PRINT "There are"; NumKeys; "keys pending in the buffer."
-
- PRINT "The next key waiting to be read is ";
- NextKey = PeekWord%(&H40, BufferHead)
- IF NextKey AND &HFF THEN
- PRINT CHR$(34); CHR$(NextKey AND &HFF); CHR$(34)
- ELSE
- PRINT "Extended key scan code"; NextKey \ 256
- END IF
-
-
- This program starts by waiting two seconds giving you a chance to press a
- few keys. It then reads the buffer head and tail pointers, and from that
- calculates the number of keys that are pending in the buffer. With a
- circular buffer the head address may be higher the tail address, so a
- separate test is needed to account for that.
- Next, the word at the head of the buffer is retrieved, which indicates
- the next available key. Since the head and tail pointers assume segment
- &H40, I used that instead of segment 0. PeekWord%(0, &H41E) produces less
- code than PeekWord%(&H40, &H1E); however, PeekWord%(0, &H400 + BufferHead)
- is worse than PeekWord%(&H40, BufferHead) because of the addition needed.
- Data in the keyboard buffer is always a full word, and it is up to you
- to determine if it is a normal ASCII key or an extended key's scan code.
- A normal key is indicated with a non-zero low byte, and the high byte then
- holds the physical hardware scan code which can usually be ignored. If the
- low byte instead holds a value of zero, it is an extended key and the scan
- code in the high byte indicates which one. Therefore, the BASIC statement
- NextKey AND &HFF masks the high byte, to test if the low byte is non-zero.
- If the key is extended, then NextKey \ 256 returns the value in the high
- byte. This is similar to the earlier examples that shifted bits to the
- right by dividing. Unlike the earlier tests that examined only some of the
- bits in the equipment flag, we are interested in all of the bits in the
- upper byte. Dividing by 256 copies the upper byte to the lower byte, thus
- discarding the lower byte entirely.
- You should also refer back to the StuffBuffer program shown in Chapter
- 6, which accesses the keyboard buffer directly and inserts new keystrokes.
-
-
- DISKETTE DATA
- =============
-
- There are several bytes in low memory that relate to the floppy and fixed
- disks in your PC, but most of them are best left alone. One exception,
- however, is the diskette drive motor timeout duration. Whenever a diskette
- drive is accessed, DOS first turns on the motor, and then waits a second
- or two until the motor has come up to speed. Once DOS is certain that the
- disk speed is correct, reading and writing are allowed.
- Because of the time it takes the diskette to become ready, DOS also
- keeps the motor running for two more seconds after a read or write has been
- completed. This way, if another request comes along within that time,
- further delays can be avoided because the motor is already running. If you
- know that the data your program is accessing is on a floppy disk and there
- may be pauses in the reading or writing, you can force the motor to stay
- on longer than the normal two seconds.
- The byte at address &H440 controls the motor hold time, and its value
- is decremented at every system timer tick [every 1/18th second]. When DOS
- has finished accessing a diskette, it places a value into this memory
- location. And when the value is decremented to zero the motor is turned
- off. The current motor on/off state is reflected by the byte at address
- &H43F. The program that follows shows how you can modify the timeout value
- by poking a new, higher value into address &H440 immediately after a
- command that accesses the disk.
-
-
- PRINT "Place a diskette in drive A and press a key ";
- WHILE INKEY$ = "": WEND
- FILES "A:*.*" 'this starts the motor
-
- DEF SEG = 0
- POKE &H440, 91 'force drive motor on for five seconds
-
- DO
- LOCATE 10, 1, 0
- PRINT PEEK(&H43F),
- PRINT PEEK(&H440)
- LOOP WHILE PEEK(&H440)
-
- BEEP 'watch the diskette light go out when you hear the beep
-
-
- The value you store at address &H440 is the number of timer ticks that are
- to elapse before the motor is turned off. Since a new timer tick occurs
- every 18.2 seconds, you will multiply the number of seconds times this
- value using Value% = Seconds * 18.2.
-
-
- DISPLAY ADAPTER DATA
- ====================
-
- As with the diskette data area, a lot of information is available that
- pertains to the video display, and most of it is of little use in an
- application programming context. Therefore, I will discuss only some of
- this data.
- The byte at address &H449 holds the current video mode. Unfortunately,
- there is no easy way to relate the information in this byte to the current
- BASIC SCREEN setting. Table 10-2 shows all of the possible values that
- might be present.
-
-
- Video Mode Description
- ========== =========================================
- 0 40 by 25 16-color text
- 1 40 by 25 16-color text, with color burst
- 2 80 by 25 16-color text
- 3 80 by 25 16-color text, with color burst
- 4 320 by 200 pixels 4-color graphics
- 5 320 by 200 pixels 4-color
- 6 640 by 200 pixels 2-color
- 7 80 by 25 monochrome text
- 13 320 by 200 pixels 16-color graphics
- 14 640 by 200 pixels 16-color graphics
- 15 640 by 350 pixels monochrome EGA graphics
- 16 640 by 350 pixels 16-color graphics
- 17 640 by 480 pixels 2-color graphics
- 18 640 by 480 pixels 16-color graphics
- 19 320 by 200 pixels 256-color graphics
-
- Table 10-2: The video mode value at Hex address 0000:0449
-
-
- Since you will always have set the video mode yourself with a SCREEN
- statement, there is little reason to have to read the current mode
- manually.
- The word at address &H44A tells how many columns are on the display, and
- the word at address &H44C holds the total size of the screen in bytes. In
- a normal 80 column by 25 line screen mode, the value at address &H44C will
- be 4096, even though the screen can hold only 4000 characters.
- The byte at address &H462 holds the current video page number, starting
- at page 0. Please understand that BASIC lets you set pages individually
- for writing to and displaying, and the page reported here is that which is
- visible on the monitor.
- We have already looked at the data at address &H463, which holds the CRT
- controller port address. Although this address is a full word, only the
- lower byte needs to be examined to know the type of display that is active.
- If the byte value at address &H463 is &HB4, then a monochrome monitor is
- connected and being used. If a color adapter is active the value at this
- byte will instead be &HD4.
-
-
- SYSTEM TIMER DATA
- =================
-
- Every 18th second the BIOS timer generates an interrupt that increments
- the master system timer count at address &H46C. This counter is stored as
- a four-byte long integer; the count is initialized to zero at midnight, and
- increases to a value of just over one 1.5 million at 11:59:59 pm.
- In some cases using the BIOS timer count directly can help to reduce the
- size of your programs, because BASIC's TIMER requires floating point math.
- Chapter 9 discussed some of the issue involved in benchmarking a program,
- and the examples there used TIMER to know when a new 1/18th second period
- has just started and how long a sequence of commands took. The following
- short program times a long integer assignment within a FOR/NEXT loop, and
- it uses the PeekWord function to access the BIOS timer count directly.
-
-
- Synch = PeekWord%(0, &H46C)
- DO
- Start = PeekWord%(0, &H46C)
- LOOP WHILE Synch = Start
-
- FOR X& = 1 TO 70000
- Y& = X&
- NEXT
-
- Done = PeekWord%(0, &H46C)
- PRINT Done - Start; "timer ticks have elapsed"
-
-
- Note that it is possible for this program to report an incorrect elapsed
- time, since it considers only the lower of the two timer words. If the
- count exceeded 65,535 during the course of the timing, the lower word will
- have wrapped around to a value of zero. An enhancement to this technique
- would therefore be to create a PeekLong% function that returns the entire
- four bytes in one operation. You could write such a function in assembly
- language, or use BASIC like this:
-
-
- FUNCTION PeekLong& (Segment%, Address%) STATIC
- PeekLong& = PeekWord%(Segment%, Address%) + 65536 * _
- PeekWord%(Segment%, Address% + 2)
- END FUNCTION
-
-
- Here, the PeekWord function is used to do most of the work, and the two
- words are combined into a single long integer. When many timing operations
- are needed using these functions can increase the speed of your programs,
- as well as help to avoid the inclusion of the floating point math library
- routines.
-
-
- PRINTER TIMEOUT DATA
- ====================
-
- Whenever data is sent to a parallel printer it is routed through a BIOS
- service that handles the actual communications with the printer hardware.
- If the printer is turned off or disconnected, the BIOS can detect that
- immediately, and report the error to the calling program. But when the
- printer is turned on but deselected (off-line) or if it has run out of
- paper, the BIOS waits for a certain period of time before returning with
- an error condition. This gives the operator a chance to fix the problem.
- The amount of time the BIOS waits varies from PC to PC, and even between
- different models of the same brand. The original IBM PC waited for only
- a very short time, and would occasionally report an error incorrectly when
- used with very slow printers. Modern PCs wait as long as two minutes
- before timing out, which is more than enough time to reload a new ream of
- paper. Unfortunately, if you want to test if a printer is ready before
- using it, your program may appear to hang if the printer is disabled.
- Although BASIC provides ON ERROR to trap for printer errors, many
- programmers prefer to avoid ON ERROR because it makes the program larger
- and run more slowly. Also, ON ERROR cannot avoid the long wait the BIOS
- imposes. There are several solutions to this problem.
- One is to print a flashing message at the bottom of the screen that says
- something like, "Turn on the printer!" immediately before printing, and
- then clear the message afterward:
-
- LOCATE 25, 1
- COLOR 23
- PRINT "Turn on the printer!";
- LPRINT Some$
- COLOR 7
- PRINT SPC(20)
-
- If the printer is in fact on line and ready, the message will be displayed
- and cleared so quickly that it is not likely to be noticed. Otherwise, the
- operator will see the message and take the appropriate action.
- This technique can be enhanced to instead test the printer, before
- sending any data. The most reliable way I have found to test a printer is
- to first send it a CHR$(32) space character, and if that is accepted print
- a CHR$(8) backspace to cancel the original space. A further enhancement
- alters the BIOS printer timeout values stored beginning at address &H478.
- The combined demonstration and function that follows performs this service
- using CALL Interrupt to circumvent BASIC's normal error handling routine.
-
- DEFINT A-Z
- DECLARE SUB INTERRUPT (IntNo, InRegs AS ANY, OutRegs AS ANY)
- DECLARE FUNCTION LPTReady% (LPTNumber)
-
- '$INCLUDE: 'REGTYPE.BI'
-
- LPTNumber = 1
-
- IF LPTReady%(LPTNumber) THEN
- PRINT "The printer is on-line and ready to go."
- ELSE
- PRINT "Sorry, the printer is not available."
- END IF
- END
-
- FUNCTION LPTReady% (LPTNumber) STATIC
-
- DIM Regs AS RegType 'for CALL INTERRUPT
- LPTReady% = 0 'assume not ready
-
- Address = &H477 + LPTNumber 'LPT timeout address
- DEF SEG = 0 'access segment zero
- OldValue = PEEK(Address) 'save current setting
- POKE Address, 1 '1 retry
-
- Regs.AX = 32 'first print a space
- Regs.DX = LPTNumber - 1 'convert to 0-based
- CALL INTERRUPT(&H17, Regs, Regs) 'print the space
-
- Result = (Regs.AX \ 256) OR 128 'get AH, ignore busy
- Result = Result AND 191 'and acknowledge
- IF Result = 144 THEN 'it worked!
- Regs.AX = 8 'print a backspace
- CALL INTERRUPT(&H17, Regs, Regs) ' to undo CHR$(32)
- LPTReady% = -1 'return success
- END IF
-
- POKE Address, OldValue 'restore original
- ' timeout value
- END FUNCTION
-
- There are several important points worth mentioning here. First, you must
- never use zero for the printer timeout value, or the timeout will be a *lot*
- longer than you anticipated. A value of zero tells the BIOS to continue
- trying indefinitely, and is equivalent to using the DOS MODE LPT1: command
- with the ",p" argument.
- Another point is that you should not use this function many times in a
- row, without ever printing anything. All modern printers provide a buffer,
- which accepts characters as fast as the computer can send them. If the
- buffer fills with spaces and backspaces before any printable characters are
- sent, it may be impossible to clear the buffer. Therefore, you should
- perform the printer test only once or twice, just before you actually need
- to begin printing.
-
-
- EGA AND VGA DATA
- ================
-
- The seven bytes starting at address &H484 hold information about an
- installed EGA or VGA display adapter. This data should not be relied upon
- until you have determined that the adapter is in fact an EGA or VGA. The
- Monitor function shown in Chapter 6 can be used for this.
- The first byte holds the number of rows currently displayed on the
- screen. The next word at addresses &H485 and &H486 tells how high each
- character is in scan lines. For a normal 80 by 25 line screen this value
- will be 16. After using WIDTH , 43 or WIDTH , 50 the height of each
- character is 8 scan lines. Notice that this value also includes the
- spacing between each line. Curiously, two bytes are set aside to hold this
- value, even though it is extremely unlikely that any video mode would ever
- require a number larger than 255.
- The only other information you are likely to find useful in this data
- area is the amount of installed memory on the EGA or VGA adapter card.
- Bits 5 and 6 at address &H487 hold the number of 64K banks, and the code
- that follows shows how to turn this into a meaningful number:
-
- DEF SEG = 0 'look in segment zero
- Byte = PEEK(&H487) 'get the byte
- Byte = Byte AND 96 'keep what we need (96 = 1100000b)
- Byte = Byte \ 32 'shift the bits right five places
- Byte = (Byte + 1) * 64 'add 1 because 0 means 64K
- PRINT "This EGA/VGA adapter has"; Byte; "K memory"
-
- After reading the EGA Features byte (listed earlier in Figure 10-1), the
- statement Byte = Byte AND 96 masks off all of the bits that are irrelevant.
- Byte is then divided by 32 to slide those bits into the lowest position.
- The number that results is coded such that 0 means 64K of installed video
- memory, 1 means 128K, 2 means 192K (which is never really possible), and
- 3 indicates 256K. Because this value is zero-based, 1 is added to Byte
- before multiplying by 64.
-
-
- MISCELLANEOUS DATA
- ==================
-
- The 16-byte data area that begins at address &H4F0 is called the inter-
- application communications area, and it is available for any arbitrary use
- by a program. One possibility is for passing just a few parameters between
- separate programs, instead of having to use COMMON and CHAIN. Although
- this data area has been available since the original IBM PC was introduced,
- there is a risk involved with using it because it is possible that another
- program or TSR has stored information there. Chapter 9 described using the
- last 96 bytes in the display adapter's memory, which is both a larger
- buffer and is probably safer to use.
- The byte at address &H500 is used as a flag by the BIOS Print Screen
- service to detect when it is busy. When you press Shift-PrtSc, the BIOS
- routine that handles that key sets this byte to a value of 1 before
- beginning to print the screen. This way if you press Shift-PrtSc again
- before it has finished printing, the second request can be ignored. When
- the printing has completed the flag is then reset to zero.
- You can set this flag manually to disable the action of the PrtSc key,
- and then reenable it again later:
-
- DEF SEG = 0
- POKE &H500, 1
- .
- .
- POKE &H500, 0
-
- In fact, you must be *sure* to reenable PrtSc before ending your program if
- you have disabled it. Otherwise, that key will be disabled until the PC
- is rebooted.
- The last low memory address I'll describe is also one of the most
- potentially useful. For systems that have only one diskette drive, the
- byte at address &H504 tells which drive (A or B) is currently active. In
- this case, that drive serves as both A and B. Most PC users are familiar
- with DOS' infamous "Insert disk for drive B" message. This message is
- displayed whenever you attempt to access one of the logical drives while
- the other is currently active.
- The problem is that this message will ruin an otherwise attractive
- screen design, and you have no control over where or if the message is
- displayed. Fortunately, you can determine if only one drive is available,
- and also which is currently active. Even better, you can set this byte to
- reflect either drive, and thus avoid the intervention by DOS.
- If the byte at address &H504 is currently zero, then drive A is active;
- a value of 1 indicates drive B. The short complete program that follows
- shows how to detect which drive is current.
-
-
- DEF SEG = 0
- Floppies% = (PEEK(&H410) AND 192) \ 64 + 1
- PRINT "This PC has"; Floppies%; "floppy disk drive(s)."
-
- IF Floppies% = 1 THEN
- PRINT "The disk is now acting as drive ";
- CurDrive% = PEEK(&H504)
- IF CurDrive% THEN
- PRINT "B"
- ELSE
- PRINT "A"
- END IF
- END IF
-
-
- To change from drive A to B simply use POKE &H504, 1, assuming that the
- current DEF SEG value is already zero. Likewise, to change from B to A you
- will use POKE &H504, 0. Of course, you must also prompt the user to change
- disks as DOS would. But at least you can control how the prompt message
- is displayed. If you do switch drives behind DOS' back, it is up to you
- to prompt the user to exchange disks as necessary, and also to ensure that
- files are updated and closed correctly before each switch.
-
-
- INPUT/OUTPUT PORTS
- ==================
-
- Besides the low memory addresses that are reserved for BIOS and DOS uses,
- every PC also has a collection of Input/Output (I/O) ports. Like memory,
- ports are addressed by number, and data may be read from or to written to
- them. In truth, some ports are write-only, others may only be read, and
- still others can be read and written.
- Where conventional memory is often used by the operating system to hold
- flags, status words, and other values, ports are used to actually control
- the hardware. For example, port number &H3F2 controls the diskette drive
- motors, and appropriate OUT commands to that port can turn the motor for
- any drive on or off.
- For the most part, you should not experiment with the ports unless you
- know what they are for, and which values are appropriate. As an example,
- it is possible to damage your monitor by sending incorrect values through
- the display adapter controller ports. Two useful ports I will describe
- here control the PC's speaker and the keyboard.
- Although BASIC offers the SOUND and PLAY statements, using them can
- quickly increase the size of a program. Both of these commands can operate
- in the background, thereby continuing to produce sound after they return
- to your program. As you can imagine, this requires a lot of code to
- implement. An informal test showed that adding a single SOUND statement
- increased the program size by more than 11K. Therefore, if you do not need
- the ability to have tones play in the background, the combination
- demonstration and subprogram that follows can be used in place of SOUND.
- Besides avoiding the code to plays tones as a background task, this routine
- also avoids SOUND's inclusion of floating point math.
-
- DEFINT A-Z
- DECLARE SUB BSound (Frequency, Duration)
-
- CLS
-
- PRINT "Sweep sound"
- FOR X = 1 TO 10
- READ Frequency
- CALL BSound(Frequency, 1)
- NEXT
- DATA 100, 200, 300, 400, 600, 900, 1200, 1500, 1800, 2100
-
- PRINT "Press a key for more..."
- WHILE INKEY$ = "": WEND
-
- PRINT "Telephone"
- FOR X = 1 TO 10
- CALL BSound(600, 1)
- CALL BSound(800, 1)
- NEXT
-
- PRINT "Press a key for more..."
- WHILE INKEY$ = "": WEND
-
- PRINT "Siren"
- FOR X = 1 TO 2
- FOR Y = 600 TO 1000 STEP 15
- CALL BSound(Y, -1) 'negative values leave
- NEXT ' the speaker turned on
- FOR Y = 1000 TO 600 STEP -15
- CALL BSound(Y, -1)
- NEXT
- NEXT
- CALL BSound(600, 1) 'force the speaker off
-
- SUB BSound (Frequency, Duration) STATIC
-
- IF Frequency < 33 THEN EXIT SUB
-
- IF NOT BeenHere THEN 'do this only once for a
- BeenHere = -1 ' smoother sound effect
- OUT &H43, 182 'initialize speaker port
- END IF
-
- Period = 1190000 \ Frequency 'convert to period
- OUT &H42, Period AND &HFF 'send it as two bytes
- OUT &H42, Period \ 256 ' in succession
-
- Speaker = INP(&H61) 'read Timer port B
- Speaker = Speaker OR 3 'set the speaker bits on
- OUT &H61, Speaker
-
- DEF SEG = 0
- FOR X = 1 TO ABS(Duration) 'for each tick specified
- ThisTime = PEEK(&H46C) ' count changes again
- DO 'wait until the timer
- LOOP WHILE ThisTime = PEEK(&H46C)
- NEXT
-
- IF Duration > 0 THEN 'turn off if requested
- Speaker = INP(&H61) 'read Timer port B
- Speaker = Speaker AND &HFC 'set the speaker bits off
- OUT &H61, Speaker
- END IF
-
- END SUB
-
- The BSound routine accepts the same frequency and duration arguments as
- BASIC's SOUND statement. Each time it is called it calculates the
- appropriate period based on the incoming frequency, which is what the timer
- ports expect. (Period is the reciprocal of frequency. Here, the period
- is related to the PC's clock frequency of 1,190,000 Hz.) BSound then turns
- on the speaker, waits in a loop for the specified duration, and finally
- turns off the speaker before returning.
- Two extra steps are required to create a smooth effect when BSound is
- called rapidly in succession. One is that the speaker port is initialized
- only once, the very first time BSound is called. The other step lets you
- optionally leave the speaker turned on when BSound returns, to avoid the
- choppiness that otherwise results with sounds like the siren effect. To
- tell BSound to leave the speaker on, use an equivalent negative value for
- the Duration parameter. Just be sure to call BSound once again with a
- positive duration value, or use the same set of INP and OUT statements
- that BSound uses to turn the speaker off. This is shown in the last
- demonstration that creates a siren sound.
-
-
- KEYBOARD PORTS
-
- There are several ports associated with the keyboard, and one is of
- particular interest. The enhanced keyboards that come with AT-class and
- later computers allow you to control how quickly keystrokes are repeated
- automatically. There are actually two values--one sets the initial delay
- before keys begin to repeat, and the other establishes the repeat rate.
- By sending the correct values through the keyboard port, you can control
- the keyboard's "typematic" response. The complete program that follows
- shows how to do this, and Table 10-3 shows how the delay and repeat rate
- values are determined.
-
- OUT &H60, &HF3 'get the keyboard's attention
- FOR D& = 1 TO 100: NEXT 'brief delay to give the hardware time to settle
- Value = 7 '1/4 second initial delay, 16 CPS
- OUT &H60, Value
-
-
-
- AT-style keyboard delay and repeat rates
- ========================================
-
- initial delay ---> 0.25 0.50 0.75 1.00
- ==== ==== ==== ====
- 30 characters per second: 0 20 40 60
- 16 characters per second: 7 27 47 67
- 8 characters per second: F 2F 4F 6F
- 4 characters per second: 17 37 57 77
- 2 characters per second: 1F 3F 5F 7F
-
- NOTE: All values are shown in Hexadecimal.
-
- Table 10-3: Sample values for setting the initial delay and repeat rate on
- an AT-style keyboard.
-
-
- Table 10-3 shows only some of the possible values that can be used.
- However, you can interpolate additional values for delay times and repeat
- rates between those shown.
-
-
- SUMMARY
- =======
-
- This chapter explained what the BIOS low memory data area is, and also
- discussed many of the addresses that are useful to application programs.
- A number of practical examples were given, including useful PEEK and POKE
- replacements that operate on data a word, rather than a byte, at a time.
- A simple binary conversion function was shown, to help you determine the
- correct values to use with AND and OR.
- You learned how to exchange serial and parallel port addresses, and how
- to access communications ports 3 and 4 which BASIC normally does not allow.
- Exchanging printer ports lets you access any printer as LPT1, perhaps to
- avoid having to rewrite a large program that relies on existing LPRINT
- statements. Other useful printer data that can be accessed is the BIOS
- timeout value, and a routine was shown for testing the printer status
- without the usual delay.
- The equipment list word was described in detail, showing how to
- determine the number of diskette drives and other peripherals that are
- installed. Another useful routine showed how to determine if drive A or
- B is active on a one-floppy system, and also how to change the current
- status of that drive. The various keyboard status bits were also
- described, and code fragments showed how to read and set the current state.
- Finally, you learned how the hardware ports are read and written using
- INP and OUT commands. One example produced sound with much less generated
- code than BASIC's SOUND, and another showed how to alter the typematic rate
- on enhanced (AT) keyboards.
- The next chapter explores using CALL Interrupt in great detail, using
- many examples that show how to access DOS and BIOS system services.